/*HTTP 处理POST请求 
设计一个简单的查询页面 index.html，地址为http://localhost/search
该页面显示一个查询框
点击提交按钮，以POST的方式提交上述请求，并返回相应结果。
*/

/*
实训内容2 IP拦截
参考nginx，设计自己的ip黑名单，并在自己的web服务器中加载该黑名单。
当用户请求时，提取用户的ip地址，如果在黑名单中，则返回403的页面。
*/

/*
实训内容3 IP拦截
思考:如何实现针对IP地址的动态拦截?比如针对1分数超过10次的ip地址进行拦截
1)如何获取ip地址?
2)如何按时间进行统计?
3)如何将异常的ip地址加入黑名单文件
创建定时任务脚本blackip.sh
读取access.log里面的记录
按照时间单位对ip进行计数
判断值的ip是否在blacklist.conf，不在就追加到blacklist.conf
重新配置文件
使用crontab设置定时任务* /1**** /bin/bash /data/blackip.sh
*/

/*
实训内容1一日志记录
参考nginx的日志，设计一套日志记录方案用于记录HTTP Server的日志。要求如下
系统日志和访问日志分开记录，写入不同的日志文件系统日志设计不同级别，常见的包括DEBUG、INFO、WARNINGERROR等。
访问日志需要记录用户的ip，访问地址，响应状态码，响应长度等
*/

#include <stdio.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <arpa/inet.h>

#define ISspace(x) isspace((int)(x))

#define SERVER_STRING "Server: MyWebserver's http/0.1.0\r\n" //定义个人server名称

#define MAX_BLACKLIST_SIZE 100

char blacklist[MAX_BLACKLIST_SIZE][16]; // 假设IP地址最大长度为15，数组大小为100

// 访问日志的文件指针 
FILE *system_log;
FILE *access_log;

void *accept_request(void* client);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);
void load_blacklist(const char *);
int is_in_blacklist(const char *); 
void write_system_log(const char*, const char*);
void write_access_log(const char*, const char*, int, size_t);
void debug_log(const char *);
void info_log(const char *);
void warning_log(const char *);
void error_die(const char *);
void logo();

// 系统级日志
void write_system_log(const char* level, const char* message) {
    time_t now;
    time(&now);
    char timestamp[80];
    struct tm* local_time = localtime(&now);
    strftime(timestamp, 80, "%Y-%m-%d %H:%M:%S", local_time);
    fprintf(system_log, "[%s] %s - %s\n", level, timestamp, message);
    fflush(system_log);  // 立即将日志写入文件
}

// 访问级日志
void write_access_log(const char* ip, const char* url, int status_code, size_t response_length) {
    time_t now;
    time(&now);
    char timestamp[80];
    struct tm* local_time = localtime(&now);
    strftime(timestamp, 80, "%Y-%m-%d %H:%M:%S", local_time);
    fprintf(access_log, "%s - [%s] \"%s\" %d %zu\n", ip, timestamp, url, status_code, response_length);
    fflush(access_log);  // 立即将日志写入文件
}

// 处理监听到的 HTTP 请求
void *accept_request(void* from_client)
{
    int client = *(int *)from_client;
    char buf[1024];
    int numchars;
    char method[255];
    char url[255];
    char path[512];
    size_t i, j;
    struct stat st; 
    int cgi = 0;     
    char *query_string = NULL;

    // 响应长度
    size_t response_length = 0;
	
    // 获取客户端 IP 地址
    struct sockaddr_in addr;
    socklen_t addr_len = sizeof(addr);
    getpeername(client, (struct sockaddr*)&addr, &addr_len);
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &(addr.sin_addr), client_ip, INET_ADDRSTRLEN);
    
    numchars = get_line(client, buf, sizeof(buf));

    i = 0; 
    j = 0;
    while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
    {
        //提取其中的请求方式
        method[i] = buf[j];
        i++; 
        j++;
    }
    method[i] = '\0';

    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    {
        unimplemented(client);
        write_access_log(client_ip, NULL, 501, 0);
        return NULL;
    }

    if (strcasecmp(method, "POST") == 0)  cgi = 1;
  
    i = 0;
    while (ISspace(buf[j]) && (j < sizeof(buf)))
        j++;

    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
    {
        url[i] = buf[j];
        i++; j++;
    }
    url[i] = '\0';

    // GET请求url可能会带有?,有查询参数
    if (strcasecmp(method, "GET") == 0)
    {
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
     
        /* 如果有?表明是动态请求, 开启cgi */
        if (*query_string == '?')
        {
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }

        if (strcmp(url, "/search") == 0)
    {
        strcpy(path, "./web/index.html");
        serve_file(client, path);
        if(stat(path, &st) == 0)
        {
        	response_length = st.st_size;
        }else 
        {
        	response_length = -99;
        }
        
        write_access_log(client_ip, url, 200, response_length);
        close(client);
        return NULL;
    }
    else
    {
        sprintf(path, "./web%s", url);
    }

    if (path[strlen(path) - 1] == '/')
    {
        strcat(path, "index.html");
        
		if(stat(path, &st) == 0)
        {
        	response_length = st.st_size;
        }else 
        {
        	response_length = -99;
        }
        
        write_access_log(client_ip, url, 200, response_length);
    }

    if (stat(path, &st) == -1)
    {
        while ((numchars > 0) && strcmp("\n", buf))
        {
            numchars = get_line(client, buf, sizeof(buf));
		}
        not_found(client);
        write_access_log(client_ip, url, 404, 0);
    }
    else
    {
        if ((st.st_mode & S_IFMT) == S_IFDIR)
        {
            strcat(path, "/index.html");
        }

        if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
        {
            cgi = 1;
        }

        if (!cgi)
        {
            serve_file(client, path);
            if(stat(path, &st) == 0)
        	{
        		response_length = st.st_size;
        	}else 
        	{
        		response_length = -99;
        	}
            write_access_log(client_ip, url, 200, response_length);
        }
        else
        {
            execute_cgi(client, path, method, query_string);
            if(stat(path, &st) == 0)
        	{
        		response_length = st.st_size;
        	}else 
        	{
        		response_length = -99;
        	}
            write_access_log(client_ip, url, 200, response_length);
        }
    }

    close(client);
    printf("connection close....client: %d \n",client);
    return NULL;
}

void bad_request(int client)
{
    	char buf[1024];
    	//发送400
	sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
	 send(client, buf, sizeof(buf), 0);
	 sprintf(buf, "Content-type: text/html\r\n");
	 send(client, buf, sizeof(buf), 0);
	 sprintf(buf, "\r\n");
	 send(client, buf, sizeof(buf), 0);
	 sprintf(buf, "<P>Your browser sent a bad request, ");
	 send(client, buf, sizeof(buf), 0);
	 sprintf(buf, "such as a POST without a Content-Length.\r\n");
	 send(client, buf, sizeof(buf), 0);
}

void cat(int client, FILE *resource)
{
	//发送文件的内容
	 char buf[1024];
	 fgets(buf, sizeof(buf), resource);
	 while (!feof(resource))
	 {
		  send(client, buf, strlen(buf), 0);
		  fgets(buf, sizeof(buf), resource);
	 }
}

void cannot_execute(int client)
{
	 char buf[1024];
	//发送500
	 sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "Content-type: text/html\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
	 send(client, buf, strlen(buf), 0);
}

// 四个等级
void debug_log(const char *sc)
{
	write_system_log("DEBUG", sc);
}

void info_log(const char *sc)
{
	write_system_log("INFO", sc);	
}

void warning_log(const char *sc)
{
	write_system_log("WARNING", sc);	
}

void error_die(const char *sc)
{
	 perror(sc);
	 write_system_log("ERROR", sc);
	 exit(1);
}

//执行cgi动态解析
void execute_cgi(int client, const char *path,
                 const char *method, const char *query_string)
{
	 char buf[1024];
	 int cgi_output[2];
	 int cgi_input[2];
	 
	 pid_t pid;
	 int status;

	 int i;
	 char c;

	 int numchars = 1;
	 int content_length = -1;
	 //默认字符
	 buf[0] = 'A'; 
	 buf[1] = '\0';
	 if (strcasecmp(method, "GET") == 0)

		 while ((numchars > 0) && strcmp("\n", buf))
		 {
			 numchars = get_line(client, buf, sizeof(buf));
		 }
	 else    
	 {
		  numchars = get_line(client, buf, sizeof(buf));
		  while ((numchars > 0) && strcmp("\n", buf))
		  {
				buf[15] = '\0';
			   if (strcasecmp(buf, "Content-Length:") == 0)
					content_length = atoi(&(buf[16]));

			   numchars = get_line(client, buf, sizeof(buf));
		  }

		  if (content_length == -1) {
		   bad_request(client);
		   return;
		   }
	 }

	 sprintf(buf, "HTTP/1.0 200 OK\r\n");
	 send(client, buf, strlen(buf), 0);
	 if (pipe(cgi_output) < 0) {
		  cannot_execute(client);
		  return;
	 }
	 if (pipe(cgi_input) < 0) { 
		  cannot_execute(client);
		  return;
	 }

	 if ( (pid = fork()) < 0 ) {
		  cannot_execute(client);
		  return;
	 }
	 if (pid == 0)  /* 子进程: 运行CGI 脚本 */
	 {
		  char meth_env[255];
		  char query_env[255];
		  char length_env[255];

		  dup2(cgi_output[1], 1);
		  dup2(cgi_input[0], 0);

		  close(cgi_output[0]);//关闭了cgi_output中的读通道
		  close(cgi_input[1]);//关闭了cgi_input中的写通道

		  sprintf(meth_env, "REQUEST_METHOD=%s", method);
		  putenv(meth_env);

		  if (strcasecmp(method, "GET") == 0) {
		  //存储QUERY_STRING
		   sprintf(query_env, "QUERY_STRING=%s", query_string);
		   putenv(query_env);
		  }
		  else {   /* POST */
			//存储CONTENT_LENGTH
		   sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
		   putenv(length_env);
		  }

		  execl(path, path, NULL);//执行CGI脚本
		  exit(0);
	 } 
	 else {  
		  close(cgi_output[1]);
		  close(cgi_input[0]);
		  if (strcasecmp(method, "POST") == 0)

			 for (i = 0; i < content_length; i++) 
			   {
				recv(client, &c, 1, 0);

				write(cgi_input[1], &c, 1);
			   }

		//读取cgi脚本返回数据

		while (read(cgi_output[0], &c, 1) > 0)
			//发送给浏览器
		{			
			send(client, &c, 1, 0);
		}

		//运行结束关闭
		close(cgi_output[0]);
		close(cgi_input[1]);


		  waitpid(pid, &status, 0);
	}
	
	 // 发送请求成功页面
    const char *success_response = "HTTP/1.0 200 OK\r\n"
                                   "Content-Type: text/html\r\n"
                                   "\r\n"
                                   "<html>\n"
                                   "<head>\n"
                                   "<meta charset=\"UTF-8\">"
                                   "<title>Send Request</title>\n"
                                   "</head>\n"
                                   "<body>\n"
                                   "<h1> Request Successful </h1>\n"
                                   "<p>Your request has been successfully processed.</p>\n"
                                   "</body>\n"
                                   "</html>\n";
    send(client, success_response, strlen(success_response), 0);
}

//解析一行http报文
int get_line(int sock, char *buf, int size)
{
	 int i = 0;
	 char c = '\0';
	 int n;

	 while ((i < size - 1) && (c != '\n'))
	 {
		  n = recv(sock, &c, 1, 0);

		  if (n > 0) 
		  {
			   if (c == '\r')
			   {
				n = recv(sock, &c, 1, MSG_PEEK);
				if ((n > 0) && (c == '\n'))
				recv(sock, &c, 1, 0);
				else
				 c = '\n';
			   }
			   buf[i] = c;
			   i++; 
		  }
		  else
		   c = '\n';
	 }
	 buf[i] = '\0';
	return(i);
}

void headers(int client, const char *filename)
{
	 char buf[1024];

	 (void)filename;  /* could use filename to determine file type */
	//发送HTTP头
	 strcpy(buf, "HTTP/1.0 200 OK\r\n");
	 send(client, buf, strlen(buf), 0);
	 strcpy(buf, SERVER_STRING);
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "Content-Type: text/html\r\n");
	 send(client, buf, strlen(buf), 0);
	 strcpy(buf, "\r\n");
	 send(client, buf, strlen(buf), 0);
}

//返回403错误页面，组装信息
void wrong_request(int client)
{
	 char buf[1024];
	 sprintf(buf, "HTTP/1.0 403 WRONG REQUEST\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, SERVER_STRING);
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "Content-Type: text/html\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<HTML><TITLE>403 FORBIDDEN</TITLE>\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<BODY><h1>403 FORBIDDEN</h1>\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<P>your request because the resource specified\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "is unavailable or nonexistent.\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "</p></BODY></HTML>\r\n");
	 send(client, buf, strlen(buf), 0);
}

//返回404错误页面，组装信息
void not_found(int client)
{
	 char buf[1024];
	 sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, SERVER_STRING);
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "Content-Type: text/html\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<BODY><h1>404 Not Found</h1>\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<p>The server could not fulfill\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "your request because the resource specified\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "is unavailable or nonexistent.\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "</p></BODY></HTML>\r\n");
	 send(client, buf, strlen(buf), 0);
}

//如果不是CGI文件，也就是静态文件，直接读取文件返回给请求的http客户端即可
void serve_file(int client, const char *filename)
{
	 FILE *resource = NULL;
	 int numchars = 1;
	 char buf[1024];
	 buf[0] = 'A'; 
	 buf[1] = '\0';
	 while ((numchars > 0) && strcmp("\n", buf)) 
	 {
		 numchars = get_line(client, buf, sizeof(buf));
	 }
	
	 //打开文件
	 resource = fopen(filename, "r");
	 if (resource == NULL)
	  not_found(client);
	 else
	 {
	  headers(client, filename);
	  cat(client, resource);
	 }
	 fclose(resource);//关闭文件句柄
}

//启动服务端
int startup(u_short *port) 
{
	int httpd = 0,option;
	struct sockaddr_in name;
	//设置http socket
	httpd = socket(PF_INET, SOCK_STREAM, 0);
	if (httpd == -1)
	{	
		error_die("socket");//连接失败
	}
	
	socklen_t optlen;
	optlen = sizeof(option);
    option = 1;
    setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, optlen);
	
	 memset(&name, 0, sizeof(name));
	 name.sin_family = AF_INET;
	 name.sin_port = htons(*port);
	 name.sin_addr.s_addr = htonl(INADDR_ANY);

	 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
	  error_die("bind");//绑定失败
	 if (*port == 0)  /*动态分配一个端口 */
	 {
	  socklen_t  namelen = sizeof(name);
	  if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
	   error_die("getsockname");
	  *port = ntohs(name.sin_port);
	 }

	 if (listen(httpd, 5) < 0)
	  error_die("listen");
	 return(httpd);
}

void unimplemented(int client)
{
	 char buf[1024];
	//发送501说明相应方法没有实现
	 sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, SERVER_STRING);
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "Content-Type: text/html\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "</TITLE></HEAD>\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<BODY><h1>501<h1>\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "<P>HTTP request method not supported.\r\n");
	 send(client, buf, strlen(buf), 0);
	 sprintf(buf, "</BODY></HTML>\r\n");
	 send(client, buf, strlen(buf), 0);
}

// 加载黑名单
void load_blacklist(const char *filename) {
    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        warning_log("黑名单获取失败");
        return;
    } else {
    	info_log("打开黑名单");
        char line[16]; // 假设IP地址最大长度为15
        int count = 0;
        while (fgets(line, sizeof(line), file) && count < MAX_BLACKLIST_SIZE) {
            strncpy(blacklist[count], line, sizeof(blacklist[0]));
            count++;
        }
        fclose(file);
    }
}

void logo() {
    //Web服务器标志信息 
    printf("___________________________________________________________\n");
    printf("___________________________________________________________\n");
    printf("  __          ________ _______\n");
    printf("  \\ \\        / /  ____|  _____\\\n");
    printf("   \\ \\  /\\  / /| |____  |____) )\n");
    printf("    \\ \\/  \\/ / |  ____|  ____(   __  __     __ ___\n");
    printf("     \\  /\\  /  | |____  |____) )(__ |_ \\  /|_ |___)\n");
    printf("      \\/  \\/   |______|_______/  __)|__ \\/ |__|   \\\n");
    printf("\n");
    printf("\033[1;34m"); // 设置为亮蓝色，加粗样式
    printf("            Welcome to use the Web ");
    printf("\033[1;31m"); // 设置为亮红色，加粗样式
    printf("SERVER");
    printf("\033[1;34m"); // 设置为亮蓝色，加粗样式
    printf("!\n");
    printf("\033[0m"); // 恢复默认颜色和样式
    printf("                     Version 1.0\n\n");
    printf("                         web\n");
    printf("___________________________________________________________\n");
    printf("___________________________________________________________\n\n");
}


// 检查黑名单
int is_in_blacklist(const char *ip) {
    unsigned long client_addr = inet_addr(ip);
    if (client_addr == INADDR_NONE) {
    	warning_log("无效IP地址");
        return 1;
    }

    for (int i = 0; i < MAX_BLACKLIST_SIZE; i++) {
        if (inet_addr(blacklist[i]) == client_addr) {
            warning_log("检测到黑名单中的ip地址");
            return 1; // 在黑名单中
        }
    }
    return 0; // 不在黑名单中
}


/*****************************主函数，也就是函数入口*****************************************/

int main(void)
{
	logo();
	// 打开日志文件
	system_log = fopen("./log/system.log", "a");
	access_log = fopen("./log/access.log", "a");
	if (system_log == NULL || access_log == NULL) {
     	printf("Error opening log files\n");
    	exit(1);
	}

	 // 关闭定时任务
	 system("crontab ./shell/blackcrontab");


	int server_sock = -1;
	u_short port = 6053;//默认监听端口号 port 为6053
	int client_sock = -1;
	// 加载黑名单
	load_blacklist("./backlist/backlist.conf");
	
	struct sockaddr_in client_name;
	socklen_t client_name_len = sizeof(client_name);
	pthread_t newthread;
	server_sock = startup(&port);
 
	printf("http server_sock is %d\n", server_sock);
	printf("http running on port %d\n", port);
	while (1)
	{
		client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);
		  
		char *client_ip = inet_ntoa(client_name.sin_addr);
		printf("ip ——: %s \n", client_ip);
      	if (is_in_blacklist(client_ip)) 
      	{
          	printf("ip: %s \n", client_ip);
          	printf("Connection from blacklisted IP: %s - denied\n", client_ip);
         	wrong_request(client_sock); // 返回403
          	write_access_log(client_ip, NULL, 403, 0);
          	// close(client_sock); // 关闭连接
        } else 
        {
            printf("New connection....  ip: %s , port: %d\n",inet_ntoa(client_name.sin_addr),ntohs(client_name.sin_port));
		  	if (client_sock == -1)
			error_die("accept");

		 	if (pthread_create(&newthread , NULL, accept_request, (void*)&client_sock) != 0)
		   	{
		   		perror("pthread_create");
		   		warning_log("线程创建失败");
	 		}
	 	}

	 }
	 close(server_sock);
	 fclose(system_log);
	 fclose(access_log);
	 
	 // 关闭定时任务
	 system("crontab -r");

	return(0);
}
